﻿using System;
using System.Collections;
using System.Dynamic;
using System.Reflection;
using System.Runtime.Serialization;

using zijian666.Converts.Abstractions;
using zijian666.Converts.Core;
using zijian666.Converts.Extensions;

namespace zijian666.Converts.Dynamic;

/// <summary>
/// 自定义动态类型对象基类
/// </summary>
/// <typeparam name="T">实际值类型</typeparam>
internal abstract class DynamicObjectBase<T> : DynamicObject, IObjectReference, IEquatable<object>, IComparable,
    IComparable<object>, IConvertible, IFormattable, IObjectProxy
{
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="value">实际值</param>
    /// <param name="convertSettings">转换设置</param>
    public DynamicObjectBase(T? value, IConvertSettings? convertSettings)
    {
        Value = value;
        ConvertSettings = convertSettings ?? Converts.Settings;
        CustomType = value?.GetType() ?? typeof(T);
    }

    /// <summary>
    /// 实际值
    /// </summary>
    public T? Value { get; }
    /// <summary>
    /// 转换设置
    /// </summary>
    public IConvertSettings ConvertSettings { get; }
    /// <summary>
    /// 获取实际值
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public object? GetRealObject(StreamingContext context) => Value;

    /// <summary>
    /// 获取由此对象提供的自定义类型。
    /// </summary>
    /// <returns> 自定义类型。 </returns>
    public Type CustomType { get; }

    /// <summary>
    /// 尝试转换类型
    /// </summary>
    /// <param name="input">输入值</param>
    /// <param name="outputType">输出类型</param>
    /// <param name="result">转换成功返回结果</param>
    protected bool TryChangeType(object? input, Type outputType, out object? result)
    {
        var res = Converts.Convert(input, outputType, ConvertSettings);
        result = res.GetValueOrDefalut(default);
        return res.Success;
    }
    /// <summary>
    /// 创建动态类型
    /// </summary>
    /// <param name="value">实际值</param>
    protected dynamic CreateDynamic(object? value) => DynamicFactory.Create(value, ConvertSettings);

    /// <inheritdoc />
    public override bool TryUnaryOperation(UnaryOperationBinder binder, out object? result) => base.TryUnaryOperation(binder, out result);

    /// <inheritdoc />
    public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result) => base.TryBinaryOperation(binder, arg, out result);

    /// <summary>
    /// 是否只读
    /// </summary>
    public virtual bool IsReadOnly => false;

    /// <summary>
    /// 根据索引获取或设置属性值
    /// </summary>
    protected virtual object? this[int index]
    {
        get => null;
        set { }
    }
    /// <summary>
    /// 根据关键字获取或设置属性
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    protected virtual object? this[string name]
    {
        get => null;
        set { }
    }

    /// <summary>
    /// 根据索引参数的类型调用 <see cref=" this[int]"/> 或 <see cref="this[string]"/>
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    protected virtual object? this[object key]
    {
        get => key switch
        {
            string name => this[name],
            int i => this[i],
            _ => this[key.Convert<int>(ConvertSettings).GetValueOrDefalut(-1)]
        };

        set => _ = key switch
        {
            string name => this[name] = value,
            int i => this[i] = value,
            _ => this[key.Convert<int>(ConvertSettings).GetValueOrDefalut(-1)] = value
        };
    }

    /// <inheritdoc />
    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result)
    {
        if ((indexes is null) || (indexes.Length != 1))
        {
            result = DynamicPrimitive.Null;
            return true;
        }
        var index = indexes[0];
        var value = this[index];

        if (value is null)
        {
            result = DynamicPrimitive.Null;
            return true;
        }
        if (binder.ReturnType == typeof(object))
        {
            result = CreateDynamic(value);
            return true;
        }
        var r = value.Convert(binder.ReturnType, ConvertSettings);
        result = r.Success ? CreateDynamic(r.Value) : DynamicPrimitive.Null;
        return true;
    }

    /// <inheritdoc />
    public override bool TryGetMember(GetMemberBinder binder, out object? result)
    {
        var value = this[(object)binder.Name];
        if (value is null)
        {
            result = DynamicPrimitive.Null;
            return true;
        }
        if (binder.ReturnType == typeof(object))
        {
            result = CreateDynamic(value);
            return true;
        }
        var r = value.Convert(binder.ReturnType, ConvertSettings);
        result = r.Success ? CreateDynamic(r.Value) : DynamicPrimitive.Null;
        return true;
    }

    /// <inheritdoc />
    public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value)
    {
        if (IsReadOnly || indexes is null || indexes.Length != 1)
        {
            return false;
        }
        var index = indexes[0];
        this[index] = value;
        return true;
    }

    /// <inheritdoc />
    public override bool TrySetMember(SetMemberBinder binder, object? value)
    {
        if (IsReadOnly)
        {
            return false;
        }
        this[(object)binder.Name] = value;
        return true;
    }

    /// <inheritdoc />
    public override bool TryConvert(ConvertBinder binder, out object? result) => TryChangeType(Value, binder.ReturnType, out result);

    /// <summary>
    /// 尝试调用成员的方法
    /// </summary>
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object? result)
    {
        if (Value is null)
        {
            result = DynamicPrimitive.Null;
            return false;
        }
        try
        {
            result = CreateDynamic(Value.GetType().InvokeMember(
                binder.Name,
                BindingFlags.InvokeMethod,
                null, Value, args));
            return true;
        }
        catch
        {
            return base.TryInvokeMember(binder, args, out result);
        }
    }

    /// <inheritdoc />
    public int CompareTo(object obj) => Compare(this, obj);

    /// <summary>
    /// 隐式转换为 <seealso cref="string"/>
    /// </summary>
    /// <param name="value"></param>
    public static implicit operator string?(DynamicObjectBase<T> value) => value.Value.To<string>();

    /// <inheritdoc />
    public override bool Equals(object obj)
    {
        if (obj is null)
        {
            return Value is null;
        }
        if (ReferenceEquals(this, obj)) //相同实例
        {
            return true;
        }
        obj = (obj as IObjectReference)?.GetRealObject(new StreamingContext()) ?? obj; //获取被包装的类型
        if (Equals(Value, obj))
        {
            return true;
        }
        var result = Value.Convert(obj.GetType(), null); //尝试类型转换后比较
        return result.Success && Equals(result.Value, obj);
    }

    /// <inheritdoc />
    public override string ToString() => Value.To<string>() ?? "";

    /// <inheritdoc />
    public override int GetHashCode() => Value?.GetHashCode() ?? 0;

    private static int Compare(DynamicObjectBase<T> a, object? b)
    {
        var b1 = b as DynamicObjectBase<T>;
        if (b1 is not null)
        {
            b = b1.Value;
        }
        if (b is null)
        {
            return a is null ? 0 : 1;
        }
        if (a.Value is null)
        {
            return -1;
        }

        var comparer = Comparer.DefaultInvariant;
        if (a.TryChangeType(a.Value, b.GetType(), out var c))
        {
            return comparer.Compare(c, b);
        }
        return -1;
    }

    #region 运算符重载
    public static bool operator >(DynamicObjectBase<T> a, object b) => Compare(a, b) > 0;

    public static bool operator <(DynamicObjectBase<T> a, object b) => Compare(a, b) < 0;

    public static bool operator ==(DynamicObjectBase<T> a, object b) => a?.Equals(b) ?? b is null;

    public static bool operator !=(DynamicObjectBase<T> a, object b) => !(a?.Equals(b) ?? b is null);

    public static bool operator >=(DynamicObjectBase<T> a, object b) => Compare(a, b) >= 0;

    public static bool operator <=(DynamicObjectBase<T> a, object b) => Compare(a, b) <= 0;
    #endregion

    /// <summary>
    /// 转转为指定类型
    /// </summary>
    private Result? ConvertTo<Result>(IFormatProvider provider)
    {
        if (provider is null)
        {
            return Value.Convert<Result>(ConvertSettings).Value;
        }
        var convertSettings = new ConvertSettings();
        _ = convertSettings.SetToStringArgument(CustomType, provider);
        return Value.Convert<Result>(convertSettings.Merge(ConvertSettings)).Value;
    }
    /// <inheritdoc />
    TypeCode IConvertible.GetTypeCode() =>
        (Value as IConvertible)?.GetTypeCode() ?? TypeCode.Object;
    /// <inheritdoc />
    bool IConvertible.ToBoolean(IFormatProvider provider) =>
        (Value as IConvertible)?.ToBoolean(provider) ?? ConvertTo<bool>(provider);
    /// <inheritdoc />
    byte IConvertible.ToByte(IFormatProvider provider) =>
        (Value as IConvertible)?.ToByte(provider) ?? ConvertTo<byte>(provider);
    /// <inheritdoc />
    char IConvertible.ToChar(IFormatProvider provider) =>
        (Value as IConvertible)?.ToChar(provider) ?? ConvertTo<char>(provider);
    /// <inheritdoc />
    DateTime IConvertible.ToDateTime(IFormatProvider provider) =>
        (Value as IConvertible)?.ToDateTime(provider) ?? ConvertTo<DateTime>(provider);
    /// <inheritdoc />
    decimal IConvertible.ToDecimal(IFormatProvider provider) =>
        (Value as IConvertible)?.ToDecimal(provider) ?? ConvertTo<decimal>(provider);
    /// <inheritdoc />
    double IConvertible.ToDouble(IFormatProvider provider) =>
        (Value as IConvertible)?.ToDouble(provider) ?? ConvertTo<double>(provider);
    /// <inheritdoc />
    short IConvertible.ToInt16(IFormatProvider provider) =>
        (Value as IConvertible)?.ToInt16(provider) ?? ConvertTo<short>(provider);
    /// <inheritdoc />
    int IConvertible.ToInt32(IFormatProvider provider) =>
        (Value as IConvertible)?.ToInt32(provider) ?? ConvertTo<int>(provider);
    /// <inheritdoc />
    long IConvertible.ToInt64(IFormatProvider provider) =>
        (Value as IConvertible)?.ToInt64(provider) ?? ConvertTo<long>(provider);
    /// <inheritdoc />
    sbyte IConvertible.ToSByte(IFormatProvider provider) =>
        (Value as IConvertible)?.ToSByte(provider) ?? ConvertTo<sbyte>(provider);
    /// <inheritdoc />
    float IConvertible.ToSingle(IFormatProvider provider) =>
        (Value as IConvertible)?.ToSingle(provider) ?? ConvertTo<float>(provider);
    /// <inheritdoc />
    string IConvertible.ToString(IFormatProvider provider) =>
        (Value as IConvertible)?.ToString(provider) ?? ConvertTo<string>(provider) ?? "";
    /// <inheritdoc />
    object? IConvertible.ToType(Type conversionType, IFormatProvider provider)
    {
        if (Value is IConvertible conv)
        {
            return conv.ToType(conversionType, provider);
        }
        if (provider is null)
        {
            return Value.Convert(conversionType, ConvertSettings).Value;
        }
        var convertSettings = new ConvertSettings();
        _ = convertSettings.SetToStringArgument(CustomType, provider);
        return Value.Convert(conversionType, convertSettings.Merge(ConvertSettings)).Value;
    }
    /// <inheritdoc />
    ushort IConvertible.ToUInt16(IFormatProvider provider) =>
        (Value as IConvertible)?.ToUInt16(provider) ?? ConvertTo<ushort>(provider);
    /// <inheritdoc />
    uint IConvertible.ToUInt32(IFormatProvider provider) =>
        (Value as IConvertible)?.ToUInt32(provider) ?? ConvertTo<uint>(provider);
    /// <inheritdoc />
    ulong IConvertible.ToUInt64(IFormatProvider provider) =>
        (Value as IConvertible)?.ToUInt64(provider) ?? ConvertTo<ulong>(provider);

    /// <inheritdoc />
    string IFormattable.ToString(string format, IFormatProvider provider)
    {
        if (Value is IFormattable formattable)
        {
            return formattable.ToString(format, provider);
        }
        if (provider is null && string.IsNullOrWhiteSpace(format))
        {
            return Value.Convert<string>(ConvertSettings).Value ?? "";
        }

        var settings = new ConvertSettings();
        if (provider is not null || !string.IsNullOrWhiteSpace(format))
        {
            _ = settings.SetToStringArgument(CustomType, format, provider);
        }
        return Value.Convert<string>(settings.Merge(ConvertSettings)).Value ?? "";
    }

    /// <inheritdoc />
    object? IObjectProxy.GetActualObject() => Value;
}
